package epoch.db.gamedb;

import static epoch.db.gamedb.DbKit.NULL_PARA_ARRAY;

import java.io.IOException;
import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import epoch.db.annotation.BindTable;
import epoch.db.config.DBConfig;
import epoch.db.gamedb.dialect.MysqlDialect;
import epoch.db.gamedb.provider.DataSourceProviderType;
import epoch.db.gamedb.provider.DruidProvider;
import epoch.db.gamedb.provider.IDataSourceProvider;
import epoch.db.gamedb.provider.JdbcProvider;
import epoch.db.interfaces.IDataAccess;
import epoch.db.interfaces.IDbCacheManager;
import epoch.game.core.exception.SysException;
import epoch.game.core.util.ClassScanner;
import epoch.game.core.util.StrKit;

public class ARDataAccess implements IDataAccess {

	public ARDataAccess() {

	}

	public static final String DEFUALT_CONFIG_NAME = "CONF_MYSQL";

	private static Logger log = LogManager.getLogger(ARDataAccess.class);

	// Ar相关的配置数据
	ActiveRecord activeRecord;
	// 缓存
	IDbCacheManager dbCahceManager;

	@Override
	public void init(DBConfig config) throws IOException {
		IDataSourceProvider dsProvider=null;
		if (config.provider == DataSourceProviderType.DRUID) {
			DruidProvider dataProvider = new DruidProvider(config.DB_URL, config.DB_USER, config.DB_PASSWORD, config.DB_DRIVER);
			// 获取连接超时时间
			dataProvider.setMaxWait(60000);
			// 开启统计
			if(config.druidFilter!=null && !config.druidFilter.isEmpty()){
				dataProvider.setFilters(config.druidFilter);
			}
			dataProvider.setMaxActive(config.maxActive);
			// 开启内存泄漏日志
			if(config.removeAbandonedTime>0){
				dataProvider.setLogAbandoned(true);
				dataProvider.setRemoveAbandoned(true);
				dataProvider.setRemoveAbandonedTimeoutMillis(config.removeAbandonedTime*1000);
			}
			dataProvider.start();
			dsProvider=dataProvider;
		} else if(config.provider == DataSourceProviderType.JDBC){
			dsProvider = new JdbcProvider(config);
		}else {
			throw new SysException("datasource provider not set!");	
		}
		if(StrKit.isBlank(config.configName)){
			activeRecord = new ActiveRecord(DEFUALT_CONFIG_NAME, dsProvider);
		}else{
			activeRecord = new ActiveRecord(config.configName, dsProvider);
		}
		activeRecord.setCache(new DBCache());
		activeRecord.setDialect(new MysqlDialect());
		// 自动注册映射关系
		String[] modelPackages = config.entityBasePackages;
		mapTable(activeRecord, modelPackages);
		//初始化ar
		activeRecord.init();

		if (dbCahceManager != null) {
			dbCahceManager.start();
		}

	}
	
	@SuppressWarnings("unchecked")
    public static void mapTable(ActiveRecord arPlugin, String[] packages){
		Set<Class<Entity>> modelClasses = ClassScanner.getClasses(packages,Entity.class);
        for(Class<?> modelClass : modelClasses){
            if(Modifier.isAbstract(modelClass.getModifiers())){
                //抽象类不作处理
                continue;
            }
            BindTable bindtable = modelClass.getAnnotation(BindTable.class);
            if(bindtable==null){
            	//类名与表名一致，可以省去注解
                //arPlugin.addMapping(modelClass.getSimpleName(),(Class<Entity>)modelClass);
            }else{
                String tableName = bindtable.tableName();
                String pk = bindtable.pk();
                arPlugin.addMapping(tableName,pk,(Class<Entity>)modelClass);
            }
        }
    }

	public IDbCacheManager getDbCahceManager() {
		return dbCahceManager;
	}

	public void setDbCahceManager(IDbCacheManager dbCahceManager) {
		this.dbCahceManager = dbCahceManager;
	}

	
	public <T extends Entity> int insert(T entity) {
		Config config = getConfig(entity.tableName());
		Table table = getTable(entity.tableName());		
		StringBuilder sql = new StringBuilder();
		List<Object> paras = new ArrayList<Object>();
		
		config.dialect.forModelSave(table, entity.getAttrs(), sql, paras);
		Connection conn = null;
		PreparedStatement pst = null;
		int result = 0;
		try {
			conn = config.getConnection();
			if (config.dialect.isOracle())
				pst = conn.prepareStatement(sql.toString(), table.getPrimaryKey());
			else
				pst = conn.prepareStatement(sql.toString(), Statement.RETURN_GENERATED_KEYS);
			
			config.dialect.fillStatement(pst, paras);
			result = pst.executeUpdate();
			return result;
		} catch (Exception e) {
			throw new ActiveRecordException("insert err! entity="+entity,e);
		} finally {
			config.close(pst, conn);
		}
	}
	
	@Override
	public <T extends Entity> int[] insert(List<T> list) {
		if(list==null || list.isEmpty()){
			return new int[0];
		}
		return Db.batchSave(list, list.size());
	}
	
	@Override
	public <T extends Entity> boolean update(T entity) {
		Config config = getConfig(entity.tableName());
		if (entity.attrs.isEmpty())
			return false;
		
		Table table = getTable(entity.tableName());
		String[] pKeys = table.getPrimaryKey();
		for (String pKey : pKeys) {
			Object id = entity.attrs.get(pKey);
			if (id == null)
				throw new ActiveRecordException("You can't update model without Primary Key, " + pKey + " can not be null.");
		}
		StringBuilder sql = new StringBuilder();
		List<Object> paras = new ArrayList<Object>();
		config.dialect.forModelUpdate(table, entity.attrs, entity.attrs.keySet(), sql, paras);
		
		if (paras.size() <= 1) {	// Needn't update
			return false;
		}
		
		// --------
		Connection conn = null;
		try {
			conn = config.getConnection();
			int result = Db.update(config, conn, sql.toString(), paras.toArray());
			if (result >= 1) {
				return true;
			}
			return false;
		} catch (Exception e) {
			throw new ActiveRecordException("update err! entity="+entity,e);
		} finally {
			config.close(conn);
		}
	}
	
	@Override
	public <T extends Entity> int[] update(List<T> list) {
		if(list==null || list.isEmpty()){
			return new int[0];
		}
		return Db.batchUpdate(list, list.size());
	}

	@Override
	public <T extends Entity> boolean saveOrUpdate(T entity) {
		Config config = getConfig(entity.tableName());
		if (entity.attrs.isEmpty())
			return false;
		
		Table table = getTable(entity.tableName());
		String[] pKeys = table.getPrimaryKey();
		for (String pKey : pKeys) {
			Object id = entity.attrs.get(pKey);
			if (id == null)
				throw new ActiveRecordException("You can't update model without Primary Key, " + pKey + " can not be null.");
		}
		StringBuilder sql = new StringBuilder();
		List<Object> paras = new ArrayList<Object>();
		config.dialect.forModelUpdate(table, entity.attrs, entity.attrs.keySet(), sql, paras);
		
		if (paras.size() <= 1) {	// Needn't update
			return false;
		}
		Connection conn = null;
		PreparedStatement pst = null;
		try {
			conn = config.getConnection();
			int result = Db.update(config, conn, sql.toString(), paras.toArray());
			if (result == 0) {
				// 更新结果为0，执行插入
				sql = new StringBuilder();
				paras.clear();
				config.dialect.forModelSave(table, entity.getAttrs(), sql, paras);
				try {
					if (config.dialect.isOracle())
						pst = conn.prepareStatement(sql.toString(), table.getPrimaryKey());
					else
						pst = conn.prepareStatement(sql.toString(), Statement.RETURN_GENERATED_KEYS);
					config.dialect.fillStatement(pst, paras);
					result = pst.executeUpdate();
				} catch (Exception ex) {
					throw new ActiveRecordException("addOrUpdate->insert err! entity=" + entity, ex);
				}
			}
			return result > 0;
		} catch (Exception e) {
			throw new ActiveRecordException("addOrUpdate err! entity=" + entity, e);
		} finally {
			config.close(pst,conn);
		}
	}

	
	public boolean delete(String tableName, Object entityId) {
		return deleteById(tableName, entityId);
	}
	
	public <K> boolean deleteList(String tableName, List<K> entityIds) {
		if(entityIds==null||entityIds.isEmpty()){
			return false;
		}
		Table table = getTable(tableName);
		Config config = getConfig(tableName);
		Connection conn = null;
		try {
			conn = config.getConnection();
			StringBuilder sqlBuilder = new StringBuilder();
			sqlBuilder.append("delete from ").append(tableName).append(" where ").append(table.getPrimaryKey()[0]).append(" in (");
			for(K id : entityIds){
				sqlBuilder.append(id).append(",");
			}
			sqlBuilder.deleteCharAt(sqlBuilder.length()-1);
			sqlBuilder.append(")");
			String sql = sqlBuilder.toString();
			int update = Db.update(config, conn, sql);
			return update>0;
		} catch (Exception e) {
			StringBuilder idVal = new StringBuilder();
			entityIds.forEach((id)->idVal.append(id.toString()).append(","));
			throw new ActiveRecordException("deleteList err! tableName="+tableName+",entityIds="+idVal,e);
		} finally {
			config.close(conn);
		}
	}
	
	/**
	 * 根据主键删除一条记录（多主键）
	 * @param table
	 * @param idValues
	 * @return
	 */
	public boolean deleteById(String tableName, Object... idValues){
		if (idValues == null || idValues.length<1)
			throw new IllegalArgumentException("idValue can not be null or empty");
		Table table = getTable(tableName);
		if (idValues.length != table.getPrimaryKey().length)
			throw new IllegalArgumentException("Primary key nubmer must equals id value number");
		Config config = getConfig(table.getName());
		Connection conn = null;
		try {
			conn = config.getConnection();
			String sql = config.dialect.forModelDeleteById(table);
			int update = Db.update(config, conn, sql, idValues);
			return update>0;
		} catch (Exception e) {
			StringBuilder idVal = new StringBuilder();
			for(Object id: idValues){
				idVal.append(id.toString()).append(",");
			}
			throw new ActiveRecordException("deleteById err! tableName="+tableName+",idValues="+idVal,e);
		} finally {
			config.close(conn);
		}
	}
	
	@Override
	public <T extends Entity> T findById(String tableName, Object entityId) {
		return findByIdLoadColumns(tableName, new Object[]{entityId}, "*");
	}
	

	@Override
	public <T extends Entity> T findById(String tableName, String[] columns, Object entityId) {
		StringBuilder colStr = new StringBuilder();
		if(columns==null || columns.length<=0){
			colStr.append("*");
		}else{
			for(int i=0; i<columns.length;i++){
				String col = columns[i];
				
				colStr.append(col);
				if(i!=columns.length-1){
					colStr.append(",");
				}
			}
		}
		return findByIdLoadColumns(tableName, new Object[]{entityId}, colStr.toString());
	}
	
	/**
	 * Find model by composite id values and load specific columns only.
	 * <pre>
	 * Example:
	 * User user = User.dao.findByIdLoadColumns(new Object[]{123, 456}, "name, age");
	 * </pre>
	 * @param idValues the composite id values of the model
	 * @param columns the specific columns to load
	 */
	public <T extends Entity> T findByIdLoadColumns(String tableName, Object[] idValues, String columns) {
		Table table = getTable(tableName);
		if (table.getPrimaryKey().length != idValues.length)
			throw new IllegalArgumentException("id values error, need " + table.getPrimaryKey().length + " id value");
		String sql = getConfig(tableName).dialect.forModelFindById(table, columns);
		List<T> result = null;
		try {
			result = findByTableSql(table,sql, idValues);
		} catch (Exception e) {
			log.error("findByIdLoadColumns err! ex"+e, e);
		}
		return result==null ? null : result.size() > 0 ? result.get(0) : null;
	}

	@Override
	public int update(String tableName, String executeCommand, Object... param) {
		Config config = getConfig(tableName);
		Table table = getTable(tableName);
		StringBuilder sql = new StringBuilder("update " + table.getName());
		sql.append(" set ").append(executeCommand);
		int result = 0;
		Connection conn=null;
		try {
			conn = config.getConnection();
			result = Db.update(config, conn, sql.toString(), param);
		} catch (SQLException ex) {
			log.error("update err!,ex=" + ex, ex);
		}finally{
			config.close(conn);
		}
		return result;
	}

	@Override
	public <T extends Entity> List<T> find(String tableName, String condition, Object... params) {
		Table table = getTable(tableName);
		return find(table,"*",condition,params);
	}
	
	/**
	 * 查询实体列表
	 * @param table
	 * @param condition
	 * @param params
	 * @return
	 */
	private <T extends Entity> List<T> find(Table table, String columns, String condition, Object... params){
		StringBuilder sql = new StringBuilder();
		sql.append("select ");
		if(columns==null||columns.trim().isEmpty()){
			sql.append("* from ");
		}else{
			sql.append(columns.trim()).append(" from ");
		}
		sql.append(table.getName());
		if(condition!=null && !condition.trim().isEmpty()){
			sql.append(" where ").append(condition);
		}
		List<T> find = null;
		try {
			find = findByTableSql(table, sql.toString(), params);
		} catch (Exception ex) {
			log.error("find err! ex="+ex, ex);
		}
		return find;
	}
	
	/**
	 * @see #find(String, Object...)
	 */
	public <T extends Entity> List<T> find(String tableName, String sql) {
		return find(tableName, sql, NULL_PARA_ARRAY);
	}
	
	
	@Override
	public <T extends Entity> List<T> find(String tableName, String[] columns, String condition, Object[] params) {
		StringBuilder colStr = new StringBuilder();
		if(columns==null || columns.length<=0){
			colStr.append("*");
		}else{
			for(int i=0; i<columns.length;i++){
				String col = columns[i];
				
				colStr.append(col);
				if(i!=columns.length-1){
					colStr.append(",");
				}
			}
		}
		Table table = getTable(tableName);
		return find(table,colStr.toString(),condition,params);
	}

	
	/**
	 * Find model.
	 */
	private <T extends Entity> List<T> findByTableSql(Table table, String sql, Object... paras) throws Exception {
		Class<? extends Entity> modelClass = table.getModelClass();
		Config config = getConfig(table.getName());
		if (config.devMode)
			checkTableName(table.getName(), sql);
		Connection conn = null;
		List<T> result = null;
		PreparedStatement pst = null;
		ResultSet rs = null;
		try {
			conn = config.getConnection();
			pst = conn.prepareStatement(sql);
			config.dialect.fillStatement(pst, paras);
			rs = pst.executeQuery();
			result = EntityBuilder.build(rs, modelClass);
		} catch (Exception e) {
			throw new ActiveRecordException(e);
		} finally {
			config.close(rs,pst,conn);
		}
		
		return result;
	}


	@Override
	public <T extends Entity> T findFirst(String tableName, String condition, Object... param) {
		return findFirst(tableName, "", condition, param);
	}

	@Override
	public <T extends Entity> T findFirst(String tableName, String[] columns, String condition, Object... params) {
		StringBuilder colStr = new StringBuilder();
		if(columns==null || columns.length<=0){
			colStr.append("*");
		}else{
			for(int i=0; i<columns.length;i++){
				String col = columns[i];
				
				colStr.append(col);
				if(i!=columns.length-1){
					colStr.append(",");
				}
			}
		}
		return findFirst(tableName, colStr.toString(), condition, params);
	}
	
	/**
	 * 根据条件查询指定列
	 * @param tableName
	 * @param columnStr
	 * @param condition
	 * @param params
	 * @return
	 */
	public <T extends Entity> T findFirst(String tableName, String columnStr, String condition, Object... params) {
		Table table = getTable(tableName);
		StringBuilder sql = new StringBuilder();
		sql.append("select ");
		if(columnStr==null||columnStr.trim().isEmpty()){
			sql.append("* from ");
		}else{
			sql.append(columnStr.trim()).append(" from ");
		}
		sql.append(table.getName());
		if(condition!=null && !condition.trim().isEmpty()){
			sql.append(" where ").append(condition);
		}
		String sqlCmd = sql.toString();
		if(!sqlCmd.contains(" limit ")){
			sqlCmd =sqlCmd+" limit 1";
		}
		List<T> find = null;
		try {
			find = findByTableSql(table, sql.toString(), params);
		} catch (Exception ex) {
			log.error("find err! ex="+ex, ex);
		}
		return find.size() > 0 ? find.get(0) : null;
	}
	
	@Override
	public int queryCount(String tableName, String condition, Object... params) {
		Table table = getTable(tableName);
		StringBuilder sql = new StringBuilder();
		sql.append("select count(*) from ").append(table.getName());
		if(condition!=null && !condition.trim().isEmpty()){
			sql.append(" where ").append(condition);
		}
		int count = 0;
		Config config = DbKit.getConfigByTableName(tableName);
		Connection conn = null;
		PreparedStatement pst = null;
		ResultSet rs = null;
		try {
			conn = config.getConnection();
			pst = conn.prepareStatement(sql.toString());
			config.dialect.fillStatement(pst, params);
			rs = pst.executeQuery();
			if(rs.next()){
				count = rs.getInt(1);
			}
			return count;
		} catch (SQLException e) {
			log.error(e, e);
			throw new ActiveRecordException(e);
		} finally {
			config.close(rs, pst, conn);
		}
	}


	@Override
	public List<Record> freeQuery(String queryCommand, Object... param) {
		Config config = DbKit.getConfig(activeRecord.getConfig().name);
		Connection conn = null;
		PreparedStatement pst = null;
		ResultSet rs = null;
		try {
			conn = config.getConnection();
			pst = conn.prepareStatement(queryCommand);
			config.dialect.fillStatement(pst, param);
			rs = pst.executeQuery();
			List<Record> result = RecordBuilder.build(config, rs);
			return result;
		} catch (SQLException e) {
			log.error(e, e);
			throw new ActiveRecordException(e);
		} finally {
			config.close(rs, pst, conn);
		}
	}

	@Override
	public int deleteAll(String tableName) {
		Config config = getConfig(tableName);
		Table table = getTable(tableName);
		String sql = "delete from "+ table.getName();
		Connection conn=null;
		Statement stmt=null;
		int result=0;
		try {
			conn = config.getConnection();
			stmt = conn.createStatement();
			result = stmt.executeUpdate(sql);
		} catch (SQLException ex) {
			log.error("deleteAll err! ex="+ex, ex);
		}finally{
			config.close(stmt, conn);
		}		
		return result;
	}
	
	@Override
	public int execute(String executeCommand, Object... params) {
		Config config = DbKit.getConfig(activeRecord.getConfig().name);
		return execute(config, executeCommand, params);
	}

	@Override
	public int execute(String tableName, String executeCommand, Object... params) {
		Config config = DbKit.getConfigByTableName(tableName);
		return execute(config, executeCommand, params);
	}
	
	private int execute(Config config, String executeCommand, Object... params) {
		Connection conn = null;
		PreparedStatement pst = null;
		try {
			conn = config.getConnection();
			pst = conn.prepareStatement(executeCommand);
			config.dialect.fillStatement(pst, params);
			int ret = pst.executeUpdate();
			return ret;
		} catch (SQLException e) {
			log.error(e, e);
			throw new ActiveRecordException(e);
		} finally {
			config.close(pst, conn);
		}
	}
	
	/**
	 * 获取数据连接配置
	 * @return
	 */
	Config getConfig(String tableName) {
		return DbKit.getConfigByTableName(tableName);
	}
	
	/**
	 * 获取表信息
	 * @return
	 */
	private Table getTable(String tableName) {
		return TableMapping.me().getTable(tableName);
	}

	/**
	 * Check the table name. The table name must in sql.
	 */
	private void checkTableName(String tableName, String sql) {
		if (!sql.toLowerCase().contains(tableName.toLowerCase()))
			throw new ActiveRecordException("The table name: " + tableName + " not in your sql.");
	}
}
